﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Xml;
using System.Reflection;

namespace MyLibrary.SequenceRunner
{
    /// <summary>
    /// 配置方法的执行类
    /// </summary>
    /// <remarks>
    /// 目前只完成执行没有参数方法的类,未来的改进包括
    /// 1.更新可执行带参函数的方法
    /// 2.更新有返回值的函数的方法
    /// 3.处理出错截断流程的方法
    /// 4.日志机制
    /// </remarks>
    public class StepExecutor
    {
        private string _configPath = String.Empty;
        private Queue<ExecuteStep> _executeSequence = new Queue<ExecuteStep>();
        /// <summary>
        /// 执行步骤配置文件路径
        /// </summary>
        public string ConfigPath
        {
            get { return _configPath; }
            set { _configPath = value; }
        }

        #region 构造函数
        /// <summary>
        /// 配置步骤执行类
        /// </summary>
        public StepExecutor()
        {

        }
        /// <summary>
        /// 配置步骤执行类
        /// </summary>
        /// <param name="configPath">配置文件路径</param>
        public StepExecutor(string configPath)
        {
            this._configPath = configPath;
        }
        #endregion

        /// <summary>
        /// 读取执行配置文件中的配置内容
        /// </summary>
        /// <returns>已经载入配置信息实体</returns>
        public StepExecutor LoadExecuteSequence(string extractionPath)
        {
            if (String.IsNullOrEmpty(this._configPath)) throw new ArgumentNullException("配置文件的路径不能为空");
            if (!File.Exists(this._configPath)) throw new FileNotFoundException("找不到指定的配置文件");

            try
            {
                XmlDocument document = new XmlDocument();
                document.Load(Environment.CurrentDirectory + @"\" + extractionPath);
                XmlNodeList stepNodes = document.SelectNodes("//Step");
                if (stepNodes.Count == 0)
                    throw new ApplicationException("配置文件中不存在有效的配置节");

                //XML文件的值
                string xmlValue = String.Empty;
                foreach (XmlNode node in stepNodes)
                {
                    ExecuteStep step = new ExecuteStep();
                    //读取Assembly名称和类名
                    xmlValue = node.SelectSingleNode("Assembly").InnerText;
                    step.AssemblyPath = xmlValue.Split(',')[0].Trim();
                    step.AssemblyClass = xmlValue.Split(',')[1].Trim();
                    if(node["AssemblyParams"] != null)
                        step.AssemblyParams = node["AssemblyParams"].InnerText;

                    //读取方法集
                    XmlNodeList methodNodes = node.SelectNodes("Methods/Method");
                    foreach (XmlNode mNode in methodNodes)
                    {
                        if (!step.MethodsName.ContainsKey(mNode.InnerText))
                            step.MethodsName.Add(mNode.InnerText, mNode.Attributes["returnType"].Value);
                    }

                    //读取属性集
                    XmlNodeList attributes = node.SelectNodes("/Attributes/Name");
                    foreach (XmlNode aNode in attributes)
                    {
                        if (!step.Attributes.ContainsKey(aNode.InnerText))
                            step.MethodsName.Add(aNode.InnerText, aNode.Attributes["type"].Value);
                    }

                    if (node.SelectSingleNode("ExitOnError").InnerText.ToLower() == "true")
                        step.ExitOnError = true;
                    else
                        step.ExitOnError = false;

                    _executeSequence.Enqueue(step);
                }

                return this;
            }
            catch(Exception ex)
            {
                this._executeSequence.Clear();
                throw new ApplicationException("读取执行配置文件错误" + ex.Message);
            }
        }

        /// <summary>
        /// 执行配置步骤
        /// </summary>
        public void Execute()
        {
            if (this._executeSequence.Count == 0) throw new ArgumentException("没有需要执行的步骤");

            //创建一个新的应用程序域用于动态加载调用的Assembly
            AppDomainSetup info = new AppDomainSetup();
            info.ApplicationBase = "file:///" + Environment.CurrentDirectory;
            string friendlyName = String.Empty;
            object methodReturn = new object();
            AppDomain dom = null;
            object[] paras = null;
            try
            {
                foreach (ExecuteStep step in _executeSequence)
                {
                    if (!File.Exists(step.AssemblyPath))
                    {
                        Console.WriteLine("制定的执行程序集不存在,流程无法继续,请检查~");
                        break;
                    }
                    friendlyName = step.AssemblyPath.Substring(step.AssemblyPath.LastIndexOf('\\') + 1);
                    dom = AppDomain.CreateDomain(friendlyName, null, info);
                    //加载调用模块
                    Assembly asm = Assembly.LoadFile(step.AssemblyPath);
                    Type type = asm.GetType(step.AssemblyClass);
                    Object obj = null;
                    if (step.AssemblyParams != String.Empty)
                    {
                        paras = (object[])step.AssemblyParams.Split(',');
                        obj = Activator.CreateInstance(type, paras);
                    }
                    else
                        obj = Activator.CreateInstance(type, null);
                    //加载调用的方法
                    foreach (KeyValuePair<string, string> m in step.MethodsName)
                    {
                        MethodInfo mi = type.GetMethod(m.Key);
                        methodReturn = mi.Invoke(obj, null);
                    }
                }
                Console.WriteLine("\n全部流程结束!");
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
                Console.WriteLine(ex.InnerException.ToString());
            }
            finally
            {
                if (dom != null)
                    AppDomain.Unload(dom);
            }
        }
    }
}
